Spring通过org.springframework.aop.TargetSource
接口提供了TargetSource的概念。这个接口负责返回实现了Joint point的目标对象。TargetSource
在每次AOP代理处理方法调用时,都会被要求产生一个目标对象。
使用Spring AOP的开发者通常不需要直接使用TargetSources,但这为池,热交换和其他复杂的目标提供了强大的支持。比如,一个池化的TargetSource可以为每个调用返回不同的目标对象,并用池管理这些实例。
如果你没有特指TargetSource,会使用包装本地对象的默认实现。(正如你所期望的)每次调用返回的都是相同的目标。让我们看一下Spring提供的标准的target Source和你该如何使用它们。
当使用自定义的target source时,你的目标通常要是原型的,而非单例的。这允许Spring在需要时创建新的实例。
org.springframework.aop.target.HotSwappleTargetSource
的存在允许AOP 代理的目标在调用者仍持有它们引用的情况下替换掉它们。
改变target source的目标会被立刻生效。HotswappableTargetSource
是线程安全的。
你可以通过HotSwappableTargetSource的swap()方法
来改变目标:
HotSwappableTargetSource swapper = (HotSwappableTargetSource) beanFactory.getBean("swapper");
Object oldTarget = swapper.swap(newTarget);
XML定义如下:
<bean id="initialTarget" class="mycompany.OldTarget"/>
<bean id="swapper" class="org.springframework.aop.target.HotSwappableTargetSource">
<constructor-arg ref="initialTarget"/>
</bean>
<bean id="swappable" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetSource" ref="swapper"/>
</bean>
调用上面的swap()
会改变swappable bean的目标。持有这个引用的客户端并不会知道这个变化的过程,但是变化立即生效了。
尽管这个例子中没有添加任何的通知——并且在使用TargetSource
时不需要添加通知——当然任何TargetSource
可以和任意通知结合使用。
池化的target source提供了和无状态会话EJB相似的编程模型,池中维护着不同的实例,当方法被调用时,可以从池中获取对象。
Spring池和SLSB池关键的区别是Spring池可以应用于任何POJO。通常对Spring而言,服务可以以非入侵的方式被应用。
Spring提供了对Commons Pool 2.2开箱即用的支持,它提供了相当有效率池化实现。如果要使用这个特性,你需要将commons-pool的Jar包添加到应用程序的类路径下。也可以继承org.springframework.aop.target.AbstractPoolingTargetSource
来支持其他的池化API。
Spring框架4.2起也支持Commons Pool 1.5+。
下面是配置实例:
<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
scope="prototype">
... properties omitted
</bean>
<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPool2TargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
<property name="maxSize" value="25"/>
</bean>
<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetSource" ref="poolTargetSource"/>
<property name="interceptorNames" value="myInterceptor"/>
</bean>
注意,目标对象——这个例子中是"businessObjectTarget"——必须要是原型的。它允许PoolingTargetSource
的实现在需要的时候让池创建一个新的实例。查看Java文档了解AbstractPoolingTargetSource
和你所希望使用的具体子类的属性:最基础的属性时"maxSize",且必须要保证存在这个属性。
在这个例子中,"myInterceptor"是定义在同一个IoC容器中的拦截器。然而,在使用池时不需要指定拦截器。如果你只希望池化且没有其他的通知,那就不需要设置interceptorsNames的属性。
可以通过Spring配置让被池管理的对象能够转成org.springframework.aop.target.PoolingConfig
接口,它可以通过引入将池的当前大小和配置信息传递出来。你需要添加一个像这样的advisor:
<bean id="poolConfigAdvisor" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject" ref="poolTargetSource"/>
<property name="targetMethod" value="getPoolingConfigMixin"/>
</bean>
因为我们使用了MethodInvokingFactoryBean,advisor会在AbstractPoolingTargetSource
类调用方法时被获得。这个advisor的名字(这里时"poolingConfigAdvisor")一定要在ProxyFactoryBean的拦截器名字的列表中,以暴露被池化的对象。
可以像下面这样转型:
PoolingConfig conf = (PoolingConfig) beanFactory.getBean("businessObject");
System.out.println("Max pool size is " + conf.getMaxSize());
通查,池化管理无状态的服务对象是不必要的。我们不认为这要作为默认的选择,因为大多数无状态的对象时线程安全的,并且如果资源被缓存的话是有问题的。
更简单的池可以使用自动代理。可以为自动代理创造器设置TargetSources。
设置“原型”的target source和池化TargetSource很像。这种情况下,秒内次调用方法时,都会为目标创建新的实例。尽管在现代的JVM中,创建新对象的话费并不是很高,但(满足IoC依赖的)织入的代价可能会很高。因此在没有特别好的理由下,你不应该使用这个方法。
为了实现这点,你需要将上面的poolTargetSource
定义做些修改(为了更清晰,我还改了名字。)
<bean id="prototypeTargetSource" class="org.springframework.aop.target.PrototypeTargetSource">
<property name="targetBeanName" ref="businessObjectTarget"/>
</bean>
这里只有一个属性:目标bean的名字。它继承而来,保证了在TargetSource的各实现中命名的一致性。和pooling target source一样,(prototype target source的)目标bean必须要是原型的bean定义。
ThreadLocal
target sources在你需要为每个请求(即每个线程)创建一个对象时十分有用。ThreadLocal
的概念是在JDK级别为每个线程透明的存储资源。设置TheadLocalTargetSource
和其他说明过的target source类型很相似:
<bean id="threadlocalTargetSource" class="org.springframework.aop.target.ThreadLocalTargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
</bean>
在多线程或时多个类加载器环境中,不正确的使用ThreadLocals可能会导致严重的问题(可能引起内存泄漏)。应该考虑用其他类包装threadlocal,而不是直接使用。并且应该记得中正确为线程设置和取消(可以简单的调用
ThreadLocal.set(null)
)资源。无论什么情况下都要记住取消资源,否则可能会引发问题。Spring的ThreadLocal支持为你做了这一点,但你再使用其他没有正确的处理代码的ThreadLocal时,要考虑这点。